Skip to content

Fix viewport blanking after large programmatic scroll (setFrameSize re-entrance)#110

Merged
krzyzanowskim merged 1 commit intokrzyzanowskim:mainfrom
iliasaz:fix/setFrameSize-reentrance
Apr 28, 2026
Merged

Fix viewport blanking after large programmatic scroll (setFrameSize re-entrance)#110
krzyzanowskim merged 1 commit intokrzyzanowskim:mainfrom
iliasaz:fix/setFrameSize-reentrance

Conversation

@iliasaz
Copy link
Copy Markdown
Contributor

@iliasaz iliasaz commented Apr 27, 2026

Summary

Cmd-End → Cmd-Home on a document taller than one viewport leaves the editor blank except for the first wrapped fragment. Viewport layout is correct; rendering is clipped because contentView and contentViewportView end up pinned to ~one-line tall while the textView itself is at full document height.

Root cause

When relocateViewport(to: docStart) calls setFrameSize(width, lineHeight) from a scrolled-down position, AppKit must synchronously retract the clip view to fit the new bounds. That fires prepareContent from inside super.setFrameSize, which calls updateContentSizeIfNeeded and re-enters setFrameSize with the full document height. The recursive call correctly resizes contentView. When control returns to the outer call, newSize is still the original (small) value, and contentView.frame.size = newSize stomps the recursive call's result.

End state: textView at full height, contentView and contentViewportView at one-line height, fragment views past line 1 clipped.

Repro

Open a multi-screen word-wrapped document, Cmd-End, then Cmd-Home. The editor renders only the first wrapped fragment of paragraph 1. Resizing the window or toggling word-wrap restores rendering because both go through a fresh non-re-entrant setFrameSize.

Verified in the bundled `TextEdit.SwiftUI` example with a ~24KB document; reverting the change reproduces the blanking.

Fix

After `super.setFrameSize`, read `frame.size` (which reflects any recursive resize) rather than the stale `newSize` parameter. When no re-entrance occurred, `frame.size == newSize`, so the common path is unchanged.

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Apr 27, 2026

CLA assistant check
All committers have signed the CLA.

…e-entrance)

When `relocateViewport(to: docStart)` calls `setFrameSize(width, lineHeight)`
from a scrolled-down position, AppKit must synchronously retract the clip
view to fit the new bounds. That fires `prepareContent` from inside
`super.setFrameSize`, which calls `updateContentSizeIfNeeded` and re-enters
`setFrameSize` with the full document height. The recursive call correctly
resizes `contentView`. When control returns to the outer call, `newSize`
is still the original (small) value, and `contentView.frame.size = newSize`
stomps the recursive call's result.

End state: textView at full height, contentView and contentViewportView at
one-line height, fragment views past line 1 clipped. Manifests as
Cmd-End → Cmd-Home blanking the editor on a multi-screen word-wrapped
document. Resizing the window or toggling word-wrap restores rendering
because both go through a fresh non-re-entrant `setFrameSize`.

Fix: after `super.setFrameSize`, read `frame.size` (which reflects any
recursive resize) rather than the stale `newSize` parameter. When no
re-entrance occurred, `frame.size == newSize`, so the common path is
unchanged.
@iliasaz iliasaz force-pushed the fix/setFrameSize-reentrance branch from 114b7bb to 29844db Compare April 27, 2026 20:00
@krzyzanowskim krzyzanowskim merged commit a534d12 into krzyzanowskim:main Apr 28, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants